﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.Attributes;
using Microsoft.WowAddonStudio.FrameXml.Serialization.Support;

namespace Microsoft.WowAddonStudio.FrameXml.Serialization.LayoutFrames
{
    [Serializable]
    [DesignerCategory("code")]
    [XmlType(Namespace = "http://www.blizzard.com/wow/ui/")]
    [XmlRoot(Namespace = "http://www.blizzard.com/wow/ui/", IsNullable = false)]
    public class Frame : LayoutFrame, ISerializationObjectContainer
    {
        private ScriptContainer scriptContainer;

        /// <summary>
        /// Gets or sets the alpha level of the frame. The default value is 1.
        /// </summary>
        [XmlAttribute("alpha")]
        [DefaultValueAttribute(1f)]
        [Category("Appearance")]
        [Description("Elements can be transparent and this property used to define it.")]
        public float Alpha
        {
            get { return Properties.GetValue<float>("Alpha"); }
            set { Properties.SetValue("Alpha", value); }
        }

        /// <summary>
        /// Gets or sets the parent of the frame.
        /// </summary>
        [XmlAttribute("parent")]
        [Category("Design")]
        [ControlSelector(ShowAll = true)]
        [Description("You can define another parent for an element.")]
        public string Parent
        {
            get { return Properties.GetValue<string>("Parent"); }
            set { Properties.SetValue("Parent", value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the frame is a top-level frame. The default value is false.
        /// </summary>
        [XmlAttribute("toplevel")]
        [DefaultValue(false)]
        [Category("Appearance")]
        [Description("Make element top level.")]
        public bool TopLevel
        {
            get { return Properties.GetValue<bool>("TopLevel"); }
            set { Properties.SetValue("TopLevel", value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the frame is movable. The default value is false.
        /// </summary>
        [XmlAttribute("movable")]
        [DefaultValue(false)]
        [Category("Behavior")]
        [Description("Make frame movable or fixed. Default false.")]
        public bool Movable
        {
            get { return Properties.GetValue<bool>("Movable"); }
            set { Properties.SetValue("Movable", value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the frame is resizable. The default value is false.
        /// </summary>
        [XmlAttribute("resizable")]
        [DefaultValue(false)]
        [Category("Behavior")]
        [Description("Make frame resizable or fixed. Default false.")]
        public bool Resizable
        {
            get { return Properties.GetValue<bool>("Resizable"); }
            set { Properties.SetValue("Resizable", value); }
        }

        /// <summary>
        /// Gets or sets the value of frameStrata for the frame. The default value is PARENT.
        /// </summary>
        [XmlAttribute("frameStrata")]
        [DefaultValue(FrameStrata.Parent)]
        [Category("Appearance")]
        [Description("FrameStrata is basically which layer your frame should be on. Some words of caution: if you set your frameStrata to \"BACKGROUND\" it will be blocked from receiving mouse events unless you set frameLevel to 1 or more. Same goes for \"TOOLTIP\" regardless of frameLevel.")]
        public FrameStrata FrameStrata
        {
            get { return Properties.GetValue<FrameStrata>("FrameStrata"); }
            set { Properties.SetValue("FrameStrata", value); }
        }

        /// <summary>
        /// Gets or sets the id of the frame. The default value is 0.
        /// </summary>
        [XmlAttribute("id")]
        [DefaultValue(0)]
        [Category("Design")]
        [Description("Used for same looking element to make difference between them (much easier than by names).")]
        public int Id
        {
            get { return Properties.GetValue<int>("Id"); }
            set { Properties.SetValue("Id", value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to enable the mouse for the frame. The default value is false.
        /// </summary>
        [XmlAttribute("enableMouse")]
        [DefaultValue(false)]
        [Category("Behavior")]
        [Description("Tell the frame to receive mouse input.")]
        public bool EnableMouse
        {
            get { return Properties.GetValue<bool>("EnableMouse"); }
            set { Properties.SetValue("EnableMouse", value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to enable the keyboard for the frame. The default value is false.
        /// </summary>
        [XmlAttribute("enableKeyboard")]
        [DefaultValue(false)]
        [Category("Behavior")]
        [Description("Tell the frame to receive keyboard input.")]
        public bool EnableKeyboard
        {
            get { return Properties.GetValue<bool>("EnableKeyboard"); }
            set { Properties.SetValue("EnableKeyboard", value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the frame should be clamped to screen. The default value is false.
        /// </summary>
        [XmlAttribute("clampedToScreen")]
        [DefaultValue(false)]
        [Category("Behavior")]
        [Description("Probhits element to go out of screen. Way applied unknown, probably just moved. Default unknown.")]
        public bool ClampedToScreen
        {
            get { return Properties.GetValue<bool>("ClampedToScreen"); }
            set { Properties.SetValue("ClampedToScreen", value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the frame is protected. The default value is false.
        /// </summary>
        [XmlAttribute("protected")]
        [DefaultValue(false)]
        [Category("Appearance")]
        [Description("Can be defined only in blizzard interface files (At least no one checked yet). If set to true some special api functions can be used. Any element inherited from protected ones can use this functions too. Protected functions/actions cannot be circumvented with this attribute.")]
        public bool Protected
        {
            get { return Properties.GetValue<bool>("Protected"); }
            set { Properties.SetValue("Protected", value); }
        }

        /// <summary>
        /// Gets or sets the title region of the frame.
        /// </summary>
        [XmlElement]
        [Browsable(true)]
        [Category("Layout")]
        [Description("Define the title region of the frame.")]
        public LayoutFrame TitleRegion
        {
            get { return Properties.GetValue<LayoutFrame>("TitleRegion"); }
            set { Properties.SetValue("TitleRegion", value); }
        }

        /// <summary>
        /// Gets or sets the resize bounds of the frame.
        /// </summary>
        [XmlElement]
        [Category("Layout")]
        [Description("Define how big or small this frame can possibly be.")]
        public ResizeBounds ResizeBounds
        {
            get { return Properties.GetValue<ResizeBounds>("ResizeBounds"); }
            set { Properties.SetValue("ResizeBounds", value); }
        }

        /// <summary>
        /// Gets or sets the backdrop of the frame.
        /// </summary>
        [XmlElement]
        [Category("Appearance")]
        [Description("A Backdrop element is a fairly simple convention for creating both the backfill and edges of a frame.")]
        [Backdrop]
        public Backdrop Backdrop
        {
            get { return Properties.GetValue<Backdrop>("Backdrop"); }
            set { Properties.SetValue("Backdrop", value); }
        }

        /// <summary>
        /// Gets or sets the hit rectangle insets.
        /// </summary>
        /// <remarks>
        /// Only <c>AbsInset</c>s are supported.
        /// </remarks>
        [XmlArray]
        [XmlArrayItem("AbsInset")]
        [Browsable(false)]
        [Description("The hit rectangle insets.")]
        public Inset[] HitRectInsets
        {
            get { return Properties.GetValue<Inset[]>("HitRectInsets"); }
            set { Properties.SetValue("HitRectInsets", value); }
        }

        /// <summary>
        /// Gets or sets a single hit rectangle inset. This property is not serialized and wraps <see cref="HitRectInsets"/>.
        /// </summary>
        [XmlIgnore]
        [Description("The hit rectangle insets.")]
        public Inset HitRectInset
        {
            get
            {
                if (this.HitRectInsets != null && this.HitRectInsets.Length > 0)
                    return this.HitRectInsets[0];

                return null;
            }
            set { this.HitRectInsets = new[] { value }; }
        }

        /// <summary>
        /// Gets the list of frames.
        /// </summary>
        [XmlArray]
        [XmlArrayItem("Frame")]
        [XmlArrayItem("Button", typeof(Button))]
        [XmlArrayItem("ColorSelect", typeof(ColorSelect))]
        [XmlArrayItem("CheckButton", typeof(CheckButton))]
        [XmlArrayItem("EditBox", typeof(EditBox))]
        [XmlArrayItem("StatusBar", typeof(StatusBar))]
        [XmlArrayItem("Slider", typeof(Slider))]
        [XmlArrayItem("ScrollFrame", typeof(ScrollFrame))]
        [XmlArrayItem("MessageFrame", typeof(MessageFrame))]
        [XmlArrayItem("ScrollingMessageFrame", typeof(ScrollingMessageFrame))]
        [XmlArrayItem("SimpleHTML", typeof(SimpleHTML))]
        [XmlArrayItem("TabardModel", typeof(TabardModel))]
        [XmlArrayItem("WorldFrame", typeof(WorldFrame))]
        [XmlArrayItem("Model", typeof(Model))]
        [Browsable(false)]
        [Description("Inside this tag all child elements are located. So if you want button inside a frame you need put it inside this tag of frame.")]
        public InheritableList<Frame> Frames
        {
            get { return Properties.GetInheritableList<Frame>("Frames"); }
        }

        /// <summary>
        /// Gets the list of layers.
        /// </summary>
        [XmlArray]
        [XmlArrayItem("Layer")]
        [Browsable(false)]
        [Description("This is a place where actually defined what will be drawn on screen.")]
        public LayerList Layers
        {
            get { return Properties.GetInheritableList<Layer, LayerList>("Layers"); }
        }

        /// <summary>
        /// Gets or sets the script container.
        /// </summary>
        /// <value>The script container.</value>
        [Browsable(false)]
        [XmlElement("Scripts")]
        [Description("Retrieves lua scripts that contains this control.")]
        public ScriptContainer ScriptContainer
        {
            get
            {
                if (scriptContainer == null)
                    scriptContainer = new ScriptContainer();

                // Do not serialize empty properties.
                if (!InheritanceEnabled && scriptContainer.Count == 0)
                    return null;

                return scriptContainer;
            }
            set { scriptContainer = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether inheritance is enabled.
        /// </summary>
        /// <value></value>
        /// <remarks>
        /// The value is propagated to all non-inherited children.
        /// </remarks>
        [XmlIgnore]
        [Browsable(false)]
        public override bool InheritanceEnabled
        {
            get { return base.InheritanceEnabled; }
            set
            {
                base.InheritanceEnabled = value;

                if (this.Layers != null)
                    this.Layers.InheritanceEnabled = value;
            }
        }

        /// <summary>
        /// Gets a value indicating whether a serialization object is complete and can be rendered.
        /// </summary>
        /// <value></value>
        [XmlIgnore]
        public override bool IsComplete
        {
            get { return this.Backdrop != null && this.Backdrop.IsComplete; }
        }

        #region ISerializationObjectContainer

        /// <summary>
        /// Determines whether the container contains a serialization object.
        /// </summary>
        /// <param name="serializationObject">The serialization object.</param>
        /// <returns>
        /// True, if the container contains the serialization object; False otherwise.
        /// </returns>
        public bool Contains(SerializationObject serializationObject)
        {
            if (serializationObject is LayeredLayoutFrame)
                return Layers.ContainsLayeredLayoutFrame(((LayeredLayoutFrame)serializationObject));
            if (serializationObject is Frame)
                return Frames.Contains((Frame)serializationObject);

            return false;
        }

        /// <summary>
        /// Adds a serialization object.
        /// </summary>
        /// <param name="serializationObject">The serialization object.</param>
        void ISerializationObjectContainer.Add(SerializationObject serializationObject)
        {
            if (serializationObject == null)
                throw new ArgumentNullException("serializationObject");

            if (serializationObject is LayeredLayoutFrame)
                Layers.AddLayeredLayoutFrame((LayeredLayoutFrame)serializationObject);
            else
                if (serializationObject is Frame)
                    Frames.Add((Frame)serializationObject);
                else
                    throw new ArgumentException("Serialization object cannot be added to this container.", "serializationObject");
        }

        /// <summary>
        /// Removes a layout frame.
        /// </summary>
        /// <param name="serializationObject">The serialization object.</param>
        void ISerializationObjectContainer.Remove(SerializationObject serializationObject)
        {
            if (serializationObject == null)
                throw new ArgumentNullException("serializationObject");

            if (serializationObject is LayeredLayoutFrame)
                Layers.RemoveLayeredLayoutFrame(((LayeredLayoutFrame)serializationObject));
            if (serializationObject is Frame)
                Frames.Remove((Frame)serializationObject);
        }

        /// <summary>
        /// Gets the child serialization objects.
        /// </summary>
        /// <param name="includeInherited">A value indicating whether to include the inherited serialization objects.</param>
        /// <returns></returns>
        IEnumerable<SerializationObject> ISerializationObjectContainer.GetChildren(bool includeInherited)
        {
            return Layers.SelectMany(layer => layer.Items.GetEnumerable(includeInherited).Cast<SerializationObject>())
                .Concat(Frames.GetEnumerable(includeInherited).OfType<SerializationObject>()).Distinct();
        }

        #endregion
    }
}